package next.fire.spinus.logx.utils.http;

import javax.net.ssl.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class HttpConnector {
    private static class TrustAnyTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    public static final Map<String, String> DEF_COOKIE_MAP = new ConcurrentHashMap<String, String>();
    public static final String DEF_CHARSET = "utf-8";

    private String url;
    private int timeout;
    private boolean keepAlive;

    public HttpConnector(String url) {
        this(url, 3000, true);
    }

    public HttpConnector(String url, int timeout, boolean keepAlive) {
        this.url = url;
        this.timeout = timeout;
        this.keepAlive = keepAlive;
    }

    public HttpResult send(String body) throws IOException {
        return this.sendWithHeader("POST", new HashMap<String, String>(), body);
    }

    public HttpResult sendWithHeader(String method, Map<String, String> header, String body) throws IOException {
        URL url2 = new URL(url);
        HttpURLConnection conn = isHttps(url2) ? getHttpsConn(url2) : (HttpURLConnection) url2.openConnection();

        // socket setting
        conn.setRequestMethod(method);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setReadTimeout(timeout);
        conn.setConnectTimeout(1500);
        conn.setUseCaches(false);

        String host = url2.getHost() + ":" + url2.getPort();

        if (keepAlive) {
            this.setCookie(host, header);
            conn.setRequestProperty("Connection", "Keep-Alive");
        } else {
            conn.setRequestProperty("Connection", "close");
        }

        // write head
        if (header != null && !header.isEmpty()) {
            for (Map.Entry<String, String> entry : header.entrySet()) {
                conn.setRequestProperty(entry.getKey(), entry.getValue());
            }
        }

        // write body
        if (body != null && body.trim().length() != 0) {
            byte[] data = body.getBytes(DEF_CHARSET);

            if (conn.getRequestProperty("Content-Type") == null) {
                conn.setRequestProperty("Content-Type", "application/json; charset=" + DEF_CHARSET);
            }

            conn.setRequestProperty("Content-Length", String.valueOf(data.length));
            OutputStream os = null;
            try {
                os = conn.getOutputStream();
            } catch (Throwable t) {
                throw new HttpNotConnectionException(url2.toString());
            }
            os.write(data);
            os.flush();
        }

        int status = conn.getResponseCode();
        HttpResult result = null;
        if (status == 200 || status == 201 || status == 204) {
            result = new HttpResult(status, this.doRead(conn.getInputStream()));
        } else {
            result = new HttpResult(status, this.doRead(conn.getErrorStream()));
        }

        // conn.disconnect();
        // handle head
        return this.doHeader(host, conn, result);
    }

    public String getUrl() {
        return url;
    }

    protected void setUrl(String url) {
        this.url = url;
    }

    private HttpsURLConnection getHttpsConn(URL url) {
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new SecureRandom());
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            return conn;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private String doRead(InputStream is) throws IOException {
        byte[] buf = new byte[1024];
        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
        for (int num; (num = is.read(buf)) != -1; ) {
            baos.write(buf, 0, num);
        }
        return baos.toString("utf-8");
    }

    private HttpResult doHeader(String host, HttpURLConnection conn, HttpResult result) {
        result.setHeader("Set-Cookie", conn.getHeaderField("Set-Cookie"));
        result.setHeader("Content-Encoding", conn.getHeaderField("Content-Encoding"));
        // handle cookie
        String cookie = conn.getHeaderField("Set-Cookie");
        if (cookie != null) {
            DEF_COOKIE_MAP.put(host, cookie);
        }
        return result;
    }

    private void setCookie(String key, Map<String, String> headers) {
        String cookie = DEF_COOKIE_MAP.get(key);
        if (cookie != null) {
            headers.put("Cookie", cookie);
        }
    }

    private boolean isHttps(URL url) {
        return url.getProtocol().equalsIgnoreCase("https");
    }
}